Опануйте Scikit-learn Pipelines, щоб оптимізувати робочі процеси машинного навчання. Навчіться автоматизувати попередню обробку, навчання моделей і налаштування гіперпараметрів для надійних, відтворюваних і готових до виробництва моделей.
Scikit-learn Pipeline: Найкращий посібник з автоматизації робочого процесу машинного навчання
У світі машинного навчання побудова моделі часто зображується як гламурний завершальний крок. Однак досвідчені фахівці з даних та інженери ML знають, що шлях до надійної моделі прокладений серією важливих, часто повторюваних і схильних до помилок кроків: очищення даних, масштабування ознак, кодування категоріальних змінних тощо. Управління цими кроками окремо для наборів даних для навчання, перевірки та тестування може швидко перетворитися на логістичний кошмар, що призведе до незначних помилок і, найнебезпечніше, до витоку даних.
Саме тут на допомогу приходить Scikit-learn's Pipeline. Це не просто зручність; це фундаментальний інструмент для створення професійних, відтворюваних і готових до виробництва систем машинного навчання. Цей вичерпний посібник проведе вас через усе, що вам потрібно знати, щоб опанувати Scikit-learn Pipelines, від основних концепцій до передових технік.
Проблема: Ручний робочий процес машинного навчання
Розглянемо типове завдання навчання з учителем. Перш ніж ви навіть зможете викликати model.fit(), вам потрібно підготувати свої дані. Стандартний робочий процес може виглядати так:
- Розділіть дані: Розділіть свій набір даних на навчальний і тестовий набори. Це перший і найважливіший крок для забезпечення оцінки продуктивності вашої моделі на невидимих даних.
- Обробка відсутніх значень: Визначте та імпутуйте відсутні дані у вашому навчальному наборі (наприклад, використовуючи середнє, медіану або константу).
- Кодування категоріальних ознак: Перетворіть нечислові стовпці, такі як 'Country' або 'Product Category', у числовий формат, використовуючи такі методи, як One-Hot Encoding або Ordinal Encoding.
- Масштабування числових ознак: Приведіть усі числові ознаки до подібного масштабу, використовуючи такі методи, як стандартизація (
StandardScaler) або нормалізація (MinMaxScaler). Це важливо для багатьох алгоритмів, таких як SVM, логістична регресія та нейронні мережі. - Навчіть модель: Нарешті, підготуйте обрану вами модель машинного навчання на попередньо оброблених навчальних даних.
Тепер, коли ви хочете зробити прогнози на своєму тестовому наборі (або нових, невидимих даних), ви повинні повторити ті ж самі кроки попередньої обробки. Ви повинні застосувати ту саму стратегію імпутації (використовуючи значення, обчислене з навчального набору), ту саму схему кодування та ті ж самі параметри масштабування. Ручне відстеження всіх цих підготовлених трансформаторів є нудним і основним джерелом помилок.
Найбільший ризик тут - витік даних. Це відбувається, коли інформація з тестового набору ненавмисно просочується в процес навчання. Наприклад, якщо ви обчислюєте середнє значення для імпутації або параметри масштабування з усього набору даних перед розділенням, ваша модель неявно навчається на тестових даних. Це призводить до надмірно оптимістичної оцінки продуктивності та моделі, яка жахливо провалюється в реальному світі.
Представляємо Scikit-learn Pipelines: Автоматизоване рішення
Scikit-learn Pipeline - це об'єкт, який об'єднує кілька кроків перетворення даних і остаточний оцінювач (наприклад, класифікатор або регресор) в єдиний, уніфікований об'єкт. Ви можете думати про це як про конвеєр для ваших даних.
Коли ви викликаєте .fit() на Pipeline, він послідовно застосовує fit_transform() до кожного проміжного кроку на навчальних даних, передаючи вихід одного кроку як вхід для наступного. Нарешті, він викликає .fit() на останньому кроці, оцінювачі. Коли ви викликаєте .predict() або .transform() на Pipeline, він застосовує лише метод .transform() кожного проміжного кроку до нових даних, перш ніж зробити прогноз за допомогою остаточного оцінювача.
Ключові переваги використання Pipelines
- Запобігання витоку даних: Це найважливіша перевага. Інтегруючи всю попередню обробку в pipeline, ви гарантуєте, що перетворення вивчаються виключно на навчальних даних під час перехресної перевірки та правильно застосовуються до даних валідації/тестування.
- Простота та організація: Весь ваш робочий процес, від необроблених даних до навченої моделі, зведено в єдиний об'єкт. Це робить ваш код чистішим, більш читабельним і простішим в управлінні.
- Відтворюваність: Об'єкт Pipeline містить весь ваш процес моделювання. Ви можете легко зберегти цей єдиний об'єкт (наприклад, використовуючи `joblib` або `pickle`) і завантажити його пізніше, щоб зробити прогнози, гарантуючи, що щоразу виконуються ті ж самі кроки.
- Ефективність у пошуку по сітці: Ви можете виконувати налаштування гіперпараметрів у всьому pipeline одночасно, знаходячи найкращі параметри як для кроків попередньої обробки, так і для остаточної моделі одночасно. Ми вивчимо цю потужну функцію пізніше.
Створення вашого першого простого Pipeline
Почнемо з простого прикладу. Уявіть, що у нас є числовий набір даних і ми хочемо масштабувати дані перед навчанням моделі логістичної регресії. Ось як ви б створили pipeline для цього.
Спочатку налаштуємо наше середовище та створимо деякі зразки даних.
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.metrics import accuracy_score
# Generate some sample data
X, y = np.random.rand(100, 5) * 10, (np.random.rand(100) > 0.5).astype(int)
# Split data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
Тепер визначимо наш pipeline. Pipeline створюється шляхом надання списку кроків. Кожен крок - це кортеж, що містить назву (рядок на ваш вибір) та об'єкт трансформатора або оцінювача.
# Create the pipeline steps
steps = [
('scaler', StandardScaler()),
('classifier', LogisticRegression())
]
# Create the Pipeline object
pipe = Pipeline(steps)
# Now, you can treat the 'pipe' object as if it were a regular model.
# Let's train it on our training data.
pipe.fit(X_train, y_train)
# Make predictions on the test data
y_pred = pipe.predict(X_test)
# Evaluate the model
accuracy = accuracy_score(y_test, y_pred)
print(f"Pipeline Accuracy: {accuracy:.4f}")
Ось і все! Всього за кілька рядків ми об'єднали масштабування та класифікацію. Scikit-learn обробляє всю проміжну логіку. Коли викликається pipe.fit(X_train, y_train), спочатку викликається StandardScaler().fit_transform(X_train), а потім результат передається LogisticRegression().fit(). Коли викликається pipe.predict(X_test), він застосовує вже підготовлений масштабатор за допомогою StandardScaler().transform(X_test) перед тим, як зробити прогнози за допомогою моделі логістичної регресії.
Обробка гетерогенних даних: `ColumnTransformer`
Реальні набори даних рідко бувають простими. Вони часто містять суміш типів даних: числові стовпці, які потребують масштабування, категоріальні стовпці, які потребують кодування, і, можливо, текстові стовпці, які потребують векторизації. Простого послідовного pipeline недостатньо для цього, оскільки вам потрібно застосовувати різні перетворення до різних стовпців.
Саме тут сяє ColumnTransformer. Він дозволяє застосовувати різні трансформатори до різних підмножин стовпців у ваших даних, а потім інтелектуально об'єднує результати. Це ідеальний інструмент для використання як крок попередньої обробки у більшому pipeline.
Приклад: Об'єднання числових і категоріальних ознак
Створимо більш реалістичний набір даних з числовими та категоріальними ознаками, використовуючи pandas.
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder
from sklearn.impute import SimpleImputer
# Create a sample DataFrame
data = {
'age': [25, 30, 45, 35, 50, np.nan, 22],
'salary': [50000, 60000, 120000, 80000, 150000, 75000, 45000],
'country': ['USA', 'Canada', 'USA', 'UK', 'Canada', 'USA', 'UK'],
'purchased': [0, 1, 1, 0, 1, 1, 0]
}
df = pd.DataFrame(data)
X = df.drop('purchased', axis=1)
y = df['purchased']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Identify numerical and categorical columns
numerical_features = ['age', 'salary']
categorical_features = ['country']
Наша стратегія попередньої обробки буде такою:
- Для числових стовпців (
age,salary): Імпутуйте відсутні значення медіаною, а потім масштабуйте їх. - Для категоріальних стовпців (
country): Імпутуйте відсутні значення найбільш частою категорією, а потім виконайте one-hot кодування.
Ми можемо визначити ці кроки за допомогою двох окремих міні-pipelines.
# Create a pipeline for numerical features
numeric_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='median')),
('scaler', StandardScaler())
])
# Create a pipeline for categorical features
categorical_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='most_frequent')),
('onehot', OneHotEncoder(handle_unknown='ignore'))
])
Тепер ми використовуємо `ColumnTransformer`, щоб застосувати ці pipelines до правильних стовпців.
# Create the preprocessor with ColumnTransformer
preprocessor = ColumnTransformer(
transformers=[
('num', numeric_transformer, numerical_features),
('cat', categorical_transformer, categorical_features)
])
`ColumnTransformer` приймає список `transformers`. Кожен трансформатор - це кортеж, що містить назву, об'єкт трансформатора (який сам може бути pipeline) і список назв стовпців, до яких його потрібно застосувати.
Нарешті, ми можемо розмістити цей `preprocessor` як перший крок у нашому основному pipeline, за яким слідує наш остаточний оцінювач.
from sklearn.ensemble import RandomForestClassifier
# Create the full pipeline
full_pipeline = Pipeline(steps=[
('preprocessor', preprocessor),
('classifier', RandomForestClassifier(random_state=42))
])
# Train and evaluate the full pipeline
full_pipeline.fit(X_train, y_train)
print("Model score on test data:", full_pipeline.score(X_test, y_test))
# You can now make predictions on new raw data
new_data = pd.DataFrame({
'age': [40, 28],
'salary': [90000, 55000],
'country': ['USA', 'Germany'] # 'Germany' is an unknown category
})
predictions = full_pipeline.predict(new_data)
print("Predictions for new data:", predictions)
Зверніть увагу, як елегантно це обробляє складний робочий процес. Параметр `handle_unknown='ignore'` у `OneHotEncoder` особливо корисний для виробничих систем, оскільки він запобігає помилкам, коли в даних з'являються нові, невидимі категорії.
Розширені техніки Pipeline
Pipelines пропонують ще більше потужності та гнучкості. Розглянемо деякі розширені функції, які необхідні для професійних проектів машинного навчання.
Створення користувацьких трансформаторів
Іноді вбудованих трансформаторів Scikit-learn недостатньо. Вам може знадобитися виконати специфічне для домену перетворення, наприклад, витягти логарифм ознаки або об'єднати дві ознаки в нову. Ви можете легко створити власні користувацькі трансформатори, які легко інтегруються в pipeline.
Для цього ви створюєте клас, який успадковує від `BaseEstimator` і `TransformerMixin`. Вам потрібно лише реалізувати методи `fit()` і `transform()` (і `__init__()`, якщо потрібно).
Створимо трансформатор, який додає нову ознаку: відношення `salary` до `age`.
from sklearn.base import BaseEstimator, TransformerMixin
# Define column indices (can also pass names)
age_ix, salary_ix = 0, 1
class FeatureRatioAdder(BaseEstimator, TransformerMixin):
def __init__(self):
pass # No parameters to set
def fit(self, X, y=None):
return self # Nothing to learn during fit, so just return self
def transform(self, X):
salary_age_ratio = X[:, salary_ix] / X[:, age_ix]
return np.c_[X, salary_age_ratio] # Concatenate original X with new feature
Потім ви можете вставити цей користувацький трансформатор у свій pipeline обробки числових значень:
numeric_transformer_with_custom = Pipeline(steps=[
('imputer', SimpleImputer(strategy='median')),
('ratio_adder', FeatureRatioAdder()), # Our custom transformer
('scaler', StandardScaler())
])
Цей рівень налаштування дозволяє вам інкапсулювати всю логіку розробки ознак у pipeline, роблячи ваш робочий процес надзвичайно портативним і відтворюваним.
Налаштування гіперпараметрів за допомогою Pipelines за допомогою `GridSearchCV`
Це, мабуть, одне з найпотужніших застосувань Pipelines. Ви можете шукати найкращі гіперпараметри для всього вашого робочого процесу, включаючи кроки попередньої обробки та остаточну модель, все одночасно.
Щоб вказати, які параметри налаштовувати, ви використовуєте спеціальний синтаксис: `step_name__parameter_name`.
Розширимо наш попередній приклад і налаштуємо гіперпараметри як для імпутера в нашому preprocessor, так і для `RandomForestClassifier`.
from sklearn.model_selection import GridSearchCV
# We use the 'full_pipeline' from the ColumnTransformer example
# Define the parameter grid
param_grid = {
'preprocessor__num__imputer__strategy': ['mean', 'median'],
'classifier__n_estimators': [50, 100, 200],
'classifier__max_depth': [None, 10, 20],
'classifier__min_samples_leaf': [1, 2, 4]
}
# Create the GridSearchCV object
grid_search = GridSearchCV(full_pipeline, param_grid, cv=5, verbose=1, n_jobs=-1)
# Fit it to the data
grid_search.fit(X_train, y_train)
# Print the best parameters and score
print("Best parameters found: ", grid_search.best_params_)
print("Best cross-validation score: ", grid_search.best_score_)
# The best estimator is already refitted on the whole training data
best_model = grid_search.best_estimator_
print("Test set score with best model: ", best_model.score(X_test, y_test))
Уважно подивіться на ключі в `param_grid`:
'preprocessor__num__imputer__strategy': Це націлено на параметр `strategy` кроку `SimpleImputer` з назвою `imputer` всередині pipeline числових значень з назвою `num`, який сам знаходиться всередині `ColumnTransformer` з назвою `preprocessor`.'classifier__n_estimators': Це націлено на параметр `n_estimators` остаточного оцінювача з назвою `classifier`.
Роблячи це, `GridSearchCV` правильно намагається використати всі комбінації та знаходить оптимальний набір параметрів для всього робочого процесу, повністю запобігаючи витоку даних під час процесу налаштування, оскільки вся попередня обробка виконується всередині кожної складки перехресної перевірки.
Візуалізація та перевірка вашого Pipeline
Складні pipelines може бути важко зрозуміти. Scikit-learn надає чудовий спосіб їх візуалізації. Починаючи з версії 0.23, ви можете отримати інтерактивне HTML-представлення.
from sklearn import set_config
# Set display to 'diagram' to get the visual representation
set_config(display='diagram')
# Now, simply displaying the pipeline object in a Jupyter Notebook or similar environment will render it
full_pipeline
Це створить діаграму, яка показує потік даних через кожен трансформатор і оцінювач разом з їхніми назвами. Це неймовірно корисно для налагодження, обміну вашою роботою та розуміння структури вашої моделі.
Ви також можете отримати доступ до окремих кроків підготовленого pipeline, використовуючи їхні назви:
# Access the final classifier of the fitted pipeline
final_classifier = full_pipeline.named_steps['classifier']
print("Feature importances:", final_classifier.feature_importances_)
# Access the OneHotEncoder to see the learned categories
onehot_encoder = full_pipeline.named_steps['preprocessor'].named_transformers_['cat'].named_steps['onehot']
print("Categorical features learned:", onehot_encoder.categories_)
Поширені підводні камені та найкращі практики
- Підготовка на неправильних даних: Завжди, завжди готуйте свій pipeline ТІЛЬКИ на навчальних даних. Ніколи не готуйте його на повному наборі даних або тестовому наборі. Це основне правило для запобігання витоку даних.
- Формати даних: Пам'ятайте про формат даних, очікуваний кожним кроком. Деякі трансформатори (наприклад, у нашому власному прикладі) можуть працювати з масивами NumPy, тоді як інші зручніші з Pandas DataFrames. Scikit-learn, як правило, добре справляється з цим, але про це слід пам'ятати, особливо з користувацькими трансформаторами.
- Збереження та завантаження Pipelines: Для розгортання вашої моделі вам потрібно буде зберегти підготовлений pipeline. Стандартний спосіб зробити це в екосистемі Python - за допомогою `joblib` або `pickle`. `joblib` часто більш ефективний для об'єктів, які містять великі масиви NumPy.
import joblib # Save the pipeline joblib.dump(full_pipeline, 'my_model_pipeline.joblib') # Load the pipeline later loaded_pipeline = joblib.load('my_model_pipeline.joblib') # Make predictions with the loaded model loaded_pipeline.predict(new_data) - Використовуйте описові назви: Дайте крокам вашого pipeline та компонентам `ColumnTransformer` чіткі, описові назви (наприклад, 'numeric_imputer', 'categorical_encoder', 'svm_classifier'). Це робить ваш код більш читабельним і спрощує налаштування гіперпараметрів і налагодження.
Висновок: Чому Pipelines є обов'язковими для професійного ML
Scikit-learn Pipelines - це не просто інструмент для написання більш охайного коду; вони представляють собою зміну парадигми від ручного, схильного до помилок скриптування до систематичного, надійного та відтворюваного підходу до машинного навчання. Вони є основою надійних практик інженерії ML.
Прийнявши pipelines, ви отримаєте:
- Надійність: Ви усуваєте найпоширеніше джерело помилок у проектах машинного навчання - витік даних.
- Ефективність: Ви оптимізуєте весь свій робочий процес, від розробки ознак до налаштування гіперпараметрів, в єдиний, згуртований підрозділ.
- Відтворюваність: Ви створюєте єдиний серіалізований об'єкт, який містить всю логіку вашої моделі, що полегшує розгортання та обмін.
Якщо ви серйозно ставитеся до створення моделей машинного навчання, які надійно працюють у реальному світі, опанування Scikit-learn Pipelines не є необов'язковим - це важливо. Почніть включати їх у свої проекти сьогодні, і ви створите кращі, більш надійні моделі швидше, ніж будь-коли раніше.